package mcjunkin;
import java.util.*;
import java.io.*;
import java.math.*;
import java.security.*;

/**
	This encapsulates a folder in the system.
*/

public class MatchFolder extends Match{
	
	//the data about this folder we store for convenience
	//	maps hash        -> MatchFile   OR
	//      canon. path -> MatchFile   (if no hash)
	//When a file generates its hash, it must notify the parent
	//to remove the "canon. path" mapping and use a hash mapping
	private TreeMap<String,MatchFile> childFiles;
	private ArrayList<MatchFolder> childFolders;	

	/** Constructs a matchfile on top of the underlying file. */
	public MatchFolder(File f, MatchFolder p) throws IOException, 
		NoSuchAlgorithmException{
		super(f,p);
		childFiles = new TreeMap<String,MatchFile>();
		childFolders = new ArrayList<MatchFolder>();
	}

	/** Checks to see if two folders are equal. Two folders are equal if 
		both contain all the files of the other. In other words,
		they are equal if 
			matchfile1.isSubsetOf(matchfile2) && matchfile2.isSubsetOf(matchfile1)
	*/
		
	public boolean equals(Object o){
		try{
			//if object isnt a match folder they are not equal
			if(o == null || !(o instanceof MatchFolder)) return false;

			//query the cache to prevent unnecessary deep searching
			MatchFolder other = (MatchFolder)o;
			if(equalsCacheQuery(other)) return true;
		
			//thoroughly test this folder for equality with another folder
			return this.isSubsetOf(other,false) && other.isSubsetOf(this,false);
		}
		catch(IOException iex){ return false; }
		catch(NoSuchAlgorithmException nex){ return false; }
	}

	/** Checks to see if this folder has previously been found to equal another
		folder.
	*/
	public boolean equalsCacheQuery(MatchFolder other){
		int mc = getMatchCount();
		String otherPath = other.getFile().getAbsolutePath();
		for(int i=0; i < mc; i++){
			if( getMatch(i).getFile().
				getAbsolutePath().equals(otherPath) ) return true;
			//else
				//System.out.println("EQUALS CACHE QUERY("+i+") "+getMatch(i).getFile().
				//getAbsolutePath()+" != "+otherPath);
		}
		return false;
	}

	/** Checks to see if this folder is a subset of the other folder. In
		other words - are all the files in this folder also present in
		the other folder.
		
		The second parameter can be used to permit or forbid deep searching.
		Here is why it exists. If you know for sure that this MatchFolder's
		children (and other) have all been passed through the match algorithm, 
		then you can set permitDeepSearch to false, because the matches will
		already be in the match cache. If those conditions are not satisfied,
		then you would recursively have to match		
	*/
		
	public boolean isSubsetOf(MatchFolder other, boolean permitDeepSearch)
		throws IOException, NoSuchAlgorithmException{
		//see if all the child folders match
		//if any of our children is not present in other folder, this folder
		//is not a subset of the other folder
		for(int i=0; i < childFolders.size(); i++){
			MatchFolder mi = childFolders.get(i);
			boolean found = other.hasChild(mi, permitDeepSearch);
			//System.out.println("hasChild "+(found?"FOUND":"DID NOT FIND")+
			//	" match for "+mi.getFile().getAbsolutePath());
			if(!found) return false;
		}
		
		//see if all the child files match
		//if any of our children is not present in other folder, this folder
		//is not a subset of the other folder
		Iterator<MatchFile> it = childFiles.values().iterator();
		while(it.hasNext()){
			MatchFile mi = it.next();
			boolean found = other.hasChild(mi,permitDeepSearch);
			//System.out.println("hasChild "+(found?"FOUND":"DID NOT FIND")+
			//	" match for "+mi.getFile().getAbsolutePath());
			if(!found) return false;
		}
		return true;
	}

	/** Checks to see if this folder contains a child equal to this MatchFolder
		or not. Does a linear sc */
	public boolean hasChild(MatchFolder m, boolean permitDeepSearch){
		for(int i=0; i < childFolders.size(); i++){
			MatchFolder child = childFolders.get(i);
			if( !permitDeepSearch && child.equalsCacheQuery(m)
				|| permitDeepSearch && child.equals(m)) return true;
		}
		return false;
	}

	/** Checks to see if this folder contains a child equal to this MatchFile
		or not. Only searches files that have a hash generated. */
	public boolean hasChild(MatchFile m, boolean permitDeepSearch) 
		throws IOException, NoSuchAlgorithmException{
		if(permitDeepSearch) 
			throw new MatchException("hasChild permitDeepSearch=true "+
				"not yet implemented. Called for "+
				"MatchFile("+m.getFile().getAbsolutePath()+"");
		//if the file as are asked to match doesnt have a generated hash,
		//and we do not wish to do a deep search, then we can return false
		//safely because we will not find any matches (since there hasnt been
		//a collision in the length hashtable yet)
		if(!m.hasHash() && !permitDeepSearch) return false;
			//throw new MatchException("hasChild called "+
			//	"for MatchFile("+m.getFile().getAbsolutePath()+") that "+
			//	"does not have a generated hash.");
		return childFiles.containsKey(m.getHash());
	}

	/** Adds this Match as a child */
	public void addChild(Match m) throws IOException, 
		NoSuchAlgorithmException{
		if(m instanceof MatchFile){
			addChildFile((MatchFile)m);
		}else if(m instanceof MatchFolder){
			addChildFolder((MatchFolder)m);		
		}else{
			throw new MatchException("addChild called "+
				"for something that is not a MatchFile or MatchFolder.");
		}
	}

	/** Adds this MatchFolder as a child */
	public void addChildFolder(MatchFolder m){
		childFolders.add(m);
	}

	/** Adds this MatchFile as a child */
	public void addChildFile(MatchFile m) throws IOException,
		NoSuchAlgorithmException{
		//if it has a hash already generated, the key is the hash - otherwise
		//the key is the absolute path of the file
		String key = m.hasHash() ? m.getHash() : m.getFile().getAbsolutePath();
		//store the key/value pair in the map
		childFiles.put(key, m);
	}

	/** Called by child to notify parent that one of it's child 
		MatchFiles now has a hash */
	public void notifyChildGeneratedHash(MatchFile m) throws IOException,
		NoSuchAlgorithmException {
		//printChildFiles();
		MatchFile m2 = childFiles.remove(m.getFile().getAbsolutePath());		
		if(m2 == null) 
			throw new MatchException("notifyChildGeneratedHash "+
				"for MatchFile("+m.getFile().getAbsolutePath()+") that "+
				"is not a child of parent MatchFolder("+file.getAbsolutePath()+")");
		if(!m.hasHash()) 
			throw new MatchException("notifyChildGeneratedHash "+
				"for MatchFile("+m.getFile().getAbsolutePath()+") that "+
				"does not have a generated hash.");
		//only reinsert if the MatchFile was actually a child, and has a hash
		childFiles.put(m.getHash(), m);
	}

	/** Debug function - shows current state of child files list */
	protected void printChildFiles(){
		Iterator<String> it = childFiles.navigableKeySet().iterator();
		System.out.println("---------Current Status ("+file.getAbsolutePath()+") --------");
		while(it.hasNext()){
			System.out.println(it.next());
		}
	}

}
